/* This program is based on the irctest.c file from libircclient project, which is licensed
according to the GNU LGPL v3. However, it only has been used as the basic framework for modelling
an interactive IRC application. The actual cryptographic part is an original contribution from
the SDTP project and several parts which were found to be unnecessary for this program have been
removed. */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <gcrypt.h>

#include "libircclient.h"
#include "base64.h"

typedef struct {
    char * channel;
    char * nick;

} irc_ctx_t;

unsigned int state = 0;
unsigned int i;

bool is_init;

const char * init_str = "~~SDTP_SECURE_RANDOM_NUMBER_VERSION_0~~";
const char * cont_str = "continue";
uint16_t min_val, max_val;
uint16_t rx_min_val, rx_max_val;
uint32_t lsb;
uint32_t result;
char * tx;
char tx_rngpct[4];
char * rx_rngpct;
char local_entropy[16];
char * remote_entropy;
char local_hash[32];
char * remote_hash;
char chk_hash[32];
char xor_entropy[4];

int len;

void event_join(irc_session_t * session,
    const char * event,
        const char * origin,
            const char ** params, unsigned int count) {
    irc_cmd_user_mode(session, "+i");
    if (is_init == true && state == 0) {
        irc_cmd_msg(session, params[0], init_str);
        state++;

        return;
    }

}

void event_connect(irc_session_t * session,
    const char * event,
        const char * origin,
            const char ** params, unsigned int count) {
    irc_ctx_t * ctx = (irc_ctx_t * ) irc_get_ctx(session);

    irc_cmd_join(session, ctx -> channel, 0);
}

void event_privmsg(irc_session_t * session,
    const char * event,
        const char * origin,
            const char ** params, unsigned int count) {

    printf("'%s' sent you a private message: %s\n",
        origin ? origin : "someone",
        params[1]);

    irc_cmd_msg(session, strtok(origin, "!"), "This is an automatic client and not immediatly used by a human. The person using this program could, however, see your message and might react to it later. It might be useful to send your e-mail adress if there is a critical reason for contacting me/the person that uses this automatic client.");
}

void event_channel(irc_session_t * session,
    const char * event,
        const char * origin,
            const char ** params, unsigned int count) {
    char nickbuf[128];

    if (count != 2)
        return;

    if (is_init == false && !strcmp(params[1], init_str)) {
        irc_cmd_msg(session, params[0], cont_str);
        state++;

        return;
    }
    if (is_init == true && state == 1 && !strcmp(params[1], cont_str)) {
        min_val = htons(min_val);
        max_val = htons(max_val);

        memcpy(tx_rngpct, & min_val, 2);
        memcpy(tx_rngpct + 2, & max_val, 2);

        tx = base64(tx_rngpct, 4, & len);

        irc_cmd_msg(session, params[0], tx);

        min_val = ntohs(min_val);
        max_val = ntohs(max_val);

        state++;

        return;
    }
    if (is_init == false && state == 1) {
        rx_rngpct = unbase64(params[1], strlen(params[1]), & len);

        memcpy( & rx_min_val, rx_rngpct, 2);
        memcpy( & rx_max_val, rx_rngpct + 2, 2);

        rx_min_val = ntohs(rx_min_val);
        rx_max_val = ntohs(rx_max_val);

        if (rx_min_val == min_val && rx_max_val == max_val)
        {
            min_val = rx_min_val;
            max_val = rx_max_val;
        }
        else
        {
            printf("The remote party has proposed other random number ranges than you. Please "
                   "agree on a certain range and try again.\n");
            exit(EXIT_FAILURE);
        }

        irc_cmd_msg(session, params[0], cont_str);

        state++;

        return;
    }
    if (is_init == true && state == 2 && !strcmp(params[1], cont_str)) {
        gcry_randomize(local_entropy, 16, GCRY_VERY_STRONG_RANDOM);

        gcry_md_hash_buffer(GCRY_MD_SHA256, local_hash, local_entropy, 16);
        tx = base64(local_hash, 32, & len);

        irc_cmd_msg(session, params[0], tx);
        state++;

        return;
    }
    if (is_init == false && state == 2) {
        remote_hash = unbase64(params[1], strlen(params[1]), & len);

        gcry_randomize(local_entropy, 16, GCRY_VERY_STRONG_RANDOM);
        gcry_md_hash_buffer(GCRY_MD_SHA256, local_hash, local_entropy, 16);
        tx = base64(local_hash, 32, & len);

        irc_cmd_msg(session, params[0], tx);

        state++;

        return;
    }

    if (is_init == true && state == 3) {
        remote_hash = unbase64(params[1], strlen(params[1]), & len);

        tx = base64(local_entropy, 16, & len);
        irc_cmd_msg(session, params[0], tx);

        state++;
        return;
    }

    if (is_init == false && state == 3) {
        remote_entropy = unbase64(params[1], strlen(params[1]), & len);

        tx = base64(local_entropy, 16, & len);

        irc_cmd_msg(session, params[0], tx);

        state++;

        return;
    }

    if (is_init == true && state == 4) {
        remote_entropy = unbase64(params[1], strlen(params[1]), & len);

        gcry_md_hash_buffer(GCRY_MD_SHA256, chk_hash, remote_entropy, 16);

        if (memcmp(chk_hash, remote_hash, 32))
            abort();

        irc_cmd_msg(session, params[0], "quit");

        memcpy(xor_entropy, remote_entropy, 4);

        for (i = 0; i < 4; i++) {
            xor_entropy[i] ^= local_entropy[i];
        }

        memcpy( & lsb, xor_entropy, 4);

        printf("%u\n", lsb);

        result = (lsb % (max_val - min_val)) + min_val;

        printf("%u\n", result);

        return;
    }
    if (is_init == false && state == 4) {
        gcry_md_hash_buffer(GCRY_MD_SHA256, chk_hash, remote_entropy, 16);

        if (memcmp(chk_hash, remote_hash, 32))
            abort();

        memcpy(xor_entropy, remote_entropy, 4);

        for (i = 0; i < 4; i++) {
            xor_entropy[i] ^= local_entropy[i];
        }

        memcpy( & lsb, xor_entropy, 4);

        printf("%u\n", lsb);

        result = (lsb % (max_val - min_val)) + min_val;

        printf("%u\n", result);

        return;
    }

    if (!origin)
        return;

    irc_target_get_nick(origin, nickbuf, sizeof(nickbuf));
}

void event_numeric(irc_session_t * session, unsigned int event,
    const char * origin,
        const char ** params, unsigned int count) {
    char buf[24];
    sprintf(buf, "%d", event);
}

int main(int argc, char ** argv) {

    irc_callbacks_t callbacks;
    irc_ctx_t ctx;
    irc_session_t * s;
    unsigned short port = 6667;

    if (argc != 6 && argc != 7) {
        printf("Usage: %s <server> <nick> <channel> <min> <max> (-init)\n", argv[0]);
        return 1;
    }

    min_val = atoi(argv[4]);
    max_val = atoi(argv[5]);

    if (argc == 7)
        is_init = true;
    else
        is_init = false;

    memset( & callbacks, 0, sizeof(callbacks));

    callbacks.event_connect = event_connect;
    callbacks.event_join = event_join;
    callbacks.event_channel = event_channel;
    callbacks.event_privmsg = event_privmsg;
    callbacks.event_numeric = event_numeric;

    s = irc_create_session( & callbacks);

    if (!s) {
        printf("Could not create session\n");
        return 1;
    }

    ctx.channel = argv[3];
    ctx.nick = argv[2];

    irc_set_ctx(s, & ctx);

    if (strchr(argv[1], ':') != 0)
        port = 0;

    if (argv[1][0] == '#' && argv[1][1] == '#') {
        argv[1]++;

        irc_option_set(s, LIBIRC_OPTION_SSL_NO_VERIFY);
    }

    if (irc_connect(s, argv[1], port, 0, argv[2], 0, 0)) {
        printf("Could not connect: %s\n", irc_strerror(irc_errno(s)));
        return 1;
    }

    if (irc_run(s)) {
        printf("Could not connect or I/O error: %s\n", irc_strerror(irc_errno(s)));
        return 1;
    }

    return 1;
}